/*
 * Copyright 2012 Biz-e (info@biz-e.nl)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.biz_e.android.calllog.dao;

import static android.provider.CallLog.Calls.INCOMING_TYPE;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

import nl.biz_e.android.calllog.CallLogApp;
import nl.biz_e.android.calllog.R;
import nl.biz_e.android.calllog.model.CallLogItem;
import nl.biz_e.android.calllog.util.CallLogPrefs;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.provider.CallLog;
import android.telephony.PhoneNumberUtils;
import android.util.Log;

/**
 * DAO to access the system calendar(s).<br>
 * Needs following permissions:
 * <ul>
 * <li>android.permission.READ_CALENDAR</li>
 * <li>android.permission.WRITE_CALENDAR</li>
 * </ul>
 * 
 * @author Joost A. Bloemsma
 */
public class CalendarDao {
	private static final String DEBUG_TAG = CalendarDao.class.getSimpleName();
	private static final String NEWLINE = "\n";
	private static final String ITEMID_PREF = "ITEMID: ";
	private Context _ctx;
	private Map<Long, String> cals = new HashMap<Long, String>();
	private static CalendarDao _instance;
	
	@SuppressWarnings("static-access")
	private static final String[] EVENT_PROJECTION = new String[] {
		Calendars._ID,                          // 0
		Calendars.ACCOUNT_NAME,                 // 1
		Calendars.ACCOUNT_TYPE,                 // 2
		Calendars.CALENDAR_DISPLAY_NAME,        // 3
		Calendars.OWNER_ACCOUNT                 // 4
	};
	
	private CalendarDao(Context ctx) {
		super();
		this._ctx = ctx;
	}
	
	/**
	 * Factory method. Makes sure this is a singleton.
	 * @return Instance of this DAO
	 */
	public static CalendarDao getInstance() {
		if (_instance == null) {
			_instance = new CalendarDao(CallLogApp.getAppContext());
		}
		return _instance;
	}
	
	/**
	 * Retrieves a {@link Map}, mapping calendar ID's to calendar names of the available calendars. <br/>
	 * Actually this is a convenience method for {@link #getCalendars(boolean)}, passing {@code true} as a parameter,
	 * forcing a fresh retrieval of the map.
	 * @return A {@link Map} of calendars
	 */
	public Map<Long, String> getCalendars() {
		return getCalendars(true);
	}
	
	/**
	 * Retrieves a {@link Map}, mapping calendar ID's to calendar names of the available calendars.
	 * @param forceNew if {@code false}, the map will not be retrieved if it already is in cache. Otherwise a new Map is created.<br>
	 * The list of calendars could be changed if an account is added while the application was still active. 
	 * @return A {@link Map} of calendars
	 */
	public Map<Long, String> getCalendars(boolean forceNew) {
		if (forceNew) cals.clear();
		
		if (cals.size() == 0) {
			Cursor cur = _ctx.getContentResolver().query(Calendars.CONTENT_URI, EVENT_PROJECTION, null, null, null);
			while (cur.moveToNext()) {
				cals.put(cur.getLong(0), cur.getString(3));
			}
			cur.close();
		}
		if (Log.isLoggable(DEBUG_TAG, Log.DEBUG)) Log.d(DEBUG_TAG, "Retrieved " + cals.size() + " calendars");
		return cals;
	}
	
	/**
	 * Stores an instance of {@link CallLogItem} in the calendar as an event.<br/>
	 * The calendar where the item will be stored is read from the preferences.
	 * @param item The {@link CallLogItem} to store
	 * @return The eventId that was given to the generated (or updated) event
	 * @throws NoCalendarException If no calendar is selected yet
	 */
	public long storeItemInCalendar(CallLogItem item) throws NoCalendarException {
		
		ContentResolver cr = _ctx.getContentResolver();
		ContentValues values = new ContentValues();
		Calendar date = Calendar.getInstance();
		Long calId = CallLogPrefs.getCalendarId();
		if (calId == 0) {
			throw new NoCalendarException();
		}
		// Store the calendar id
		values.put(Events.CALENDAR_ID, calId);
		
		// Store title
		String name = item.getCachedName();
		if (name == null || "".equals(name)) {
			name = _ctx.getString(R.string.unknown);
		}
		String titleTempl = CallLogPrefs.getTitleTemplate();
		values.put(Events.TITLE, titleTempl.replace(CallLogPrefs.NAME_PLACEHOLDER, name));
		
		// Set the start date-time
		date.setTime(item.getDate());
		values.put(Events.DTSTART, date.getTimeInMillis());
		
		// Set the end date-time
		date.add(Calendar.SECOND, item.getDuration());
		values.put(Events.DTEND, date.getTimeInMillis());
		
		// Set the description
		StringBuilder sb = new StringBuilder();
		sb.append(_ctx.getString(R.string.caltxt_name));
		sb.append(": ");
		sb.append(name);
		sb.append(NEWLINE);
		
		sb.append(_ctx.getString(R.string.caltxt_number));
		sb.append(": ");
		sb.append(PhoneNumberUtils.formatNumber(item.getNumber()));
		
		String type = "";
		switch (item.getType()) {
			case INCOMING_TYPE:
				type = _ctx.getString(R.string.calltype_in);
				break;
			case CallLog.Calls.OUTGOING_TYPE:
				type = _ctx.getString(R.string.calltype_out);
				break;
			case CallLog.Calls.MISSED_TYPE:
				type = _ctx.getString(R.string.calltype_missed);
				break;
		}
		if (!"".equals(type)) {
			sb.append(" (");
			sb.append(type);
			sb.append(")");
		}
		sb.append(NEWLINE);
		
		sb.append(_ctx.getString(R.string.caltxt_duration));
		sb.append(": ");
		sb.append(Integer.toString(item.getDuration()));
		sb.append(" sec");
		
		String notes = LogDBDao.getInstance().getNotes(item, calId);
		if ((notes != null) && ("" != notes)) {
			sb.append(NEWLINE);
			sb.append(NEWLINE);
			sb.append(_ctx.getString(R.string.caltxt_notes));
			sb.append(NEWLINE);
			sb.append(notes);
		}

		sb.append(NEWLINE);
		sb.append(ITEMID_PREF);
		sb.append(item.getItemId());
		
		values.put(Events.DESCRIPTION, sb.toString());
		values.put(Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());
		
		values.put(Events.AVAILABILITY, Events.AVAILABILITY_FREE);
		Uri uri = cr.insert(Events.CONTENT_URI, values);

		// get the event ID that is the last element in the Uri
		long eventID = Long.parseLong(uri.getLastPathSegment());
		if (Log.isLoggable(DEBUG_TAG, Log.DEBUG)) Log.d(DEBUG_TAG, "Stored item " + item.getItemId() + " in calendar with eventId " + eventID);
		return eventID;
	}
}

